Stage 1

Pull in mouse cell metadata and retain eye cells.

Output the mouse ocular barcodes.

library(tidyverse)
sample_meta <- data.table::fread('~/git/scEiaD_quant/sample_meta.scEiaD_v1.2025_02_03.02.tsv.gz')
cell_meta <- data.table::fread('~/data/scEiaD_modeling/mm111.adata.solo.20250131.obs.csv.gz')[,-1] %>% 
  relocate(barcode) %>% 
  filter(solo_doublet == "FALSE")

mm111_eye <- cell_meta %>% 
  filter(
    organ == 'Eye',
    organism == 'Mus musculus',
    !grepl("^#", sample_accession),
    source == 'Tissue')

#mm111_eye$barcode %>% write(gzfile('~/git/scEiaD_modeling/data/mm111_eye_bcs.20250221.csv.gz'))
sinteractive --mem=128G --time=8:00:00  --gres=gpu:a100:1
cd /data/OGVFB_BG/scEiaD/2024_02_28/snakeout/mm111_developing_eye
source /data/$USER/conda/etc/profile.d/conda.sh && source /data/$USER/conda/etc/profile.d/mamba.sh
mamba activate rapids_singlecell
bash ct_projection_call.sh

scanvi Modeling

scEiaD Human Developing Model

sceiad_meta <- fst::read_fst('~/data/scEiaD_2022_02/meta_filter.fst')
G3;fstcore package v0.9.18
gG3;(OpenMP was not detected, using single threaded mode)
g
ctp_mm111__human_modeling <- data.table::fread('/Users/mcgaugheyd/data/scEiaD_modeling/mm111_developing_eye/mm111.eye.human_CT_projections_20120128.csv.gz') %>% select(-17) %>% left_join(sceiad_meta %>% select(barcode = Barcode, CellType_predict), by = 'barcode') %>% 
  # only look at >= 10 day cells
  mutate(age = as.numeric(age)) %>% 
  filter(age < 10)
G2;H2;Warningh: There was 1 warning in `mutate()`.
ℹ In argument: `age = as.numeric(age)`.
Caused by warning:
! NAs introduced by coerciong
label <- 'MajorCellType'
machine_label <- 'CT__sceiad_20250211_dev_mmGeneFilter'
most_common_mislabel <- ctp_mm111__human_modeling %>% 
  group_by(.data[[label]],.data[[machine_label]]) %>% 
  summarise(Count = n()) %>% 
  arrange(.data[[label]], -Count) %>% 
  slice_max(order_by = Count, n = 2) %>% 
  filter(.data[[label]] != .data[[machine_label]])
`summarise()` has grouped output by 'MajorCellType'. You can override using the `.groups` argument.
ctp_mm111__human_modeling %>% 
  mutate(tf = case_when(.data[[label]] == .data[[machine_label]] ~ TRUE,
                        TRUE ~ FALSE)) %>% 
  group_by(.data[[label]]) %>% 
  summarise(accuracy = sum(tf)/length(tf)) %>% 
  arrange(.data[[label]]) %>% 
  left_join(most_common_mislabel %>% 
              select(any_of(label), 
                     `most common mislabel` = any_of(machine_label),
                     `mislabel count` = Count))
Joining with `by = join_by(MajorCellType)`

UMAP

umap1 = 'umap1_sceiad_20250211_dev_mmGeneFilter'
umap2 = 'umap2_sceiad_20250211_dev_mmGeneFilter'
ct = 'CT__sceiad_20250211_dev_mmGeneFilter'
color = 'CT__sceiad_20250211_dev_mmGeneFilter'
score <- 'CT__sceiad_20250211_dev_mmGeneFilter__max_score'
umap_ct_plotter <- function(obj, umap1, umap2, ct, color, labels = TRUE){
  plot <- obj %>% 
    mutate(leiden = as.factor(leiden)) %>% 
    ggplot(aes(x=.data[[umap1]],y=.data[[umap2]])) +
    scattermore::geom_scattermore(aes(color = .data[[color]]), pointsize = 0.8, alpha = 0.5) +
    scale_color_manual(values = c(pals::alphabet2(), pals::glasbey(), pals::kelly(), pals::alphabet()) %>%
                         unname()) + 
    cowplot::theme_cowplot() + theme(legend.position = "none")
  if (labels){
    plot <- plot + ggrepel::geom_label_repel(data = . %>% 
                                               group_by(.data[[ct]]) %>% 
                                               summarise(across(all_of(c(umap1,umap2)), median)),
                                             aes(label = .data[[ct]], color = .data[[color]])) 
    
  } 
  print(plot)
}

umap_score_plotter <- function(obj, umap1, umap2, ct, score){
  obj %>% 
    ggplot(aes(x=.data[[umap1]],y=.data[[umap2]])) +
    scattermore::geom_scattermore(aes(color = .data[[score]]), pointsize = 0.8, alpha = 0.5) +
    ggrepel::geom_label_repel(data = . %>% 
                                group_by(.data[[ct]]) %>% 
                                summarise(across(all_of(c(umap1,umap2)), median)),
                              aes(label = .data[[ct]]), max.overlaps = Inf) + 
    scale_color_viridis_c() +
    cowplot::theme_cowplot() + theme(legend.position = "none")
}
umap_ct_plotter(ctp_mm111__human_modeling, umap1, umap2, ct, color)

umap_ct_plotter(ctp_mm111__human_modeling, umap1, umap2, 'leiden', 'leiden')

umap_score_plotter(ctp_mm111__human_modeling, umap1, umap2, ct, score)

MajorCellType <-> CT__sceiad_20250211_dev_mmGeneFilter

Looks quite good - perhaps only pericyte and ciliary margin (neither of which are in the big model) are wrong?

ctp_mm111__human_modeling %>% 
  mcHelpeRs::sum_rat('CellType_predict', 'MajorCellType', 'CT__sceiad_20250211_dev_mmGeneFilter', threshold = 0.05) %>% 
  #filter(MajorCellType != '') %>% 
  DT::datatable()

Rough set CT calls for scVI modeling

As a crucial step is subsetting of each CT to prevent a very high n set (e.g. rods) to dominate

ctp_mm111__human_modeling <- ctp_mm111__human_modeling %>% 
  mutate(rough_ct = case_when(CellType_predict %in% c("Ciliary Margin", "Pericyte") ~ tolower(CellType_predict),
                              TRUE ~ CT__sceiad_20250211_dev_mmGeneFilter))

Stage 2

Pick ref barcodes to make scVI models

set.seed(20250303)
keep_ct <- ctp_mm111__human_modeling %>% group_by(rough_ct) %>% summarise(Count = n()) %>% filter(Count > 10) %>% pull(rough_ct) %>% unique() %>% sort()


mm111_dev_eye_ref_bcs <- ctp_mm111__human_modeling %>% ungroup() %>% 
  filter(rough_ct %in% keep_ct) %>% 
  group_by(study_accession, rough_ct) %>% 
  sample_n(1000, replace = TRUE) %>% 
  unique()

mm111_dev_eye_query_bcs <- ctp_mm111__human_modeling %>% 
  ungroup() %>% 
  filter(!barcode %in% mm111_dev_eye_ref_bcs$barcode)

#mm111_dev_eye_ref_bcs$barcode %>% write(gzfile('~/git/scEiaD_modeling/data/mm111_dev_eye_ref_bcs.20250303.csv.gz'))
#mm111_dev_eye_query_bcs$barcode %>% write(gzfile('~/git/scEiaD_modeling/data/mm111_dev_eye_query_bcs.20250303.csv.gz'))
cd /data/OGVFB_BG/scEiaD/2024_02_28/snakeout/mm111_developing_eye
sbatch --time=10:00:00 snakecall.sh
source('analysis_scripts.R')
obs <- pull_obs('~/data/scEiaD_modeling/mm111_developing_eye/mm111_dev_eye_20250303_2000hvg_50e_30l.obs.csv.gz', machine_label = 'scANVI_MCT', cluster_col = 'leiden5')
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
obs$obs <- obs$obs %>% left_join(ctp_mm111__human_modeling %>% select(barcode, CT__sceiad_20250211_dev_mmGeneFilter, CT__sceiad_20250211_dev_mmGeneFilter__max_score,  umap1_sceiad_20250211_dev_mmGeneFilter,umap2_sceiad_20250211_dev_mmGeneFilter), by = c("barcodei" = 'barcode'))

obs$labels$mCT_scvi <- obs$obs %>% mcHelpeRs::sum_rat("leiden5","CT__sceiad_20250211_dev_mmGeneFilter") %>% slice_max(order_by = Count, n = 1) %>% pull(CT__sceiad_20250211_dev_mmGeneFilter)

obs$labels$mMCT_scvi <- obs$obs %>% mcHelpeRs::sum_rat("leiden5","CT__sceiad_20250211_dev_mmGeneFilter") %>% summarise(out = paste0(CT__sceiad_20250211_dev_mmGeneFilter, collapse = ', ')) %>% pull(out)

UMAPs

obs$obs %>% 
  left_join(obs$labels, by = 'leiden5') %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = scANVI_MCT), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(scANVI_MCT) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = scANVI_MCT, color = scANVI_MCT)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("MajorCellType (scANVI)")


obs$obs %>% 
  left_join(obs$labels, by = 'leiden5') %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CT__sceiad_20250211_dev_mmGeneFilter), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(CT__sceiad_20250211_dev_mmGeneFilter) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = CT__sceiad_20250211_dev_mmGeneFilter, color = CT__sceiad_20250211_dev_mmGeneFilter)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("CT__sceiad_20250211_dev_mmGeneFilter")  


obs$obs %>% 
  left_join(obs$labels, by = 'leiden5') %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CT__sceiad_20250211_dev_mmGeneFilter), pointsize = 0.8, alpha = 0.5) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("CT__sceiad_20250211_dev_mmGeneFilter") +
  coord_cartesian(xlim = c(-20,20), ylim = c(-15,15)) +
  facet_wrap(~study_accession)


obs$obs %>% 
  left_join(obs$labels, by = 'leiden5') %>% 
  mutate(age = as.integer(age)) %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = age), pointsize = 0.8, alpha = 0.5) +
  # ggrepel::geom_label_repel(data = . %>% group_by(CT__sceiad_20250211_dev_mmGeneFilter) %>% 
  #                             summarise(umap1 = median(umap1),
  #                                       umap2 = median(umap2)),
  #                           aes(label = CT__sceiad_20250211_dev_mmGeneFilter)) +
  scale_color_viridis_c(option = 'H') + 
  cowplot::theme_cowplot() +
  ggtitle("CT__sceiad_20250211_dev_mmGeneFilter") +
  coord_cartesian(xlim = c(-20,20), ylim = c(-15,15))

leiden5 UMAP

obs$obs %>%
  left_join(obs$labels, by = 'leiden5') %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = as.factor(leiden5)), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_text_repel(data = . %>% group_by(mCT_scvi, leiden5) %>%
                             summarise(umap1 = median(umap1),
                                       umap2 = median(umap2),),
                           aes(label = paste0(mCT_scvi,'-',leiden5),color = as.factor(leiden5)), bg.color = 'white') +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey(), pals::alphabet(), pals::kelly()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("leiden5") 

hclust

pb <- data.table::fread('/Users/mcgaugheyd/data/scEiaD_modeling/mm111_developing_eye/mm111_dev_eye_20250303_2000hvg_50e_30l.pseudoBulk.leiden5.csv.gz')
colnames(pb) <- gsub("\\.\\d+","",colnames(pb))
hvg <- data.table::fread('/Users/mcgaugheyd/data/scEiaD_modeling/mm111_developing_eye/hvg2000.csv.gz')[-1,]
rnames <- pb$V1
clust <- str_extract(rnames, '\\d+') %>% as.integer()
pb <- pb[,-1] %>% as.matrix()
row.names(pb) <- as.character(clust)
pb <- pb[as.character(obs$labels$leiden5),]

pb_norm <- metamoRph::normalize_data(t(pb), sample_scale = 'cpm') %>% t() 
G3;Sample CPM scaling
gG3;log1p scaling
g
pb_full <- pb_norm
pb_norm <- pb_norm[,hvg$V2]

#pb_norm <- pb_norm[,hvg$V2[!hvg$V2 %in% c(cc_genes,ribo_genes)]]
# https://stats.stackexchange.com/questions/31565/compute-a-cosine-dissimilarity-matrix-in-r
sim <- pb_norm / sqrt(rowSums(pb_norm * pb_norm))
sim <- sim %*% t(sim)
D_sim <- as.dist(1 - sim)

hclust_sim <- hclust(D_sim, method = 'average')

hclust_sim$labels <- obs$labels %>% pull(leiden5)

library(ggtree)
p <- ggtree(hclust_sim)

p$data <- p$data %>% left_join(obs$label, by = c("label" = "leiden5"))
p + layout_dendrogram() +
  geom_tiplab(aes(label = paste(label, mCT_scvi, sep = ' - '), color = mCT_scvi)) + 
  theme_dendrogram(plot.margin=margin(16,16,300,16)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  guides(color="none")

CT by CT

diff <- pull_diff("~/data/scEiaD_modeling/mm111_developing_eye/mm111_dev_eye_20250303_2000hvg_50e_30l.difftesting.leiden5.csv.gz", orgdb =  org.Mm.eg.db::org.Mm.eg.db, cluster_col = 'leiden5')
G3;
gG3;'select()' returned 1:many mapping between keys and columns
gG2;H2;Warningh in left_join(., conv_table, by = c("ENSEMBL")) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 13 of `x` matches multiple rows in `y`.
ℹ Row 22694 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g
G2;H2;Warningh in left_join(., conv_table, by = c("ENSEMBL")) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 7 of `x` matches multiple rows in `y`.
ℹ Row 6 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g
conv_table <- AnnotationDbi::select(org.Mm.eg.db::org.Mm.eg.db,
                                    keys=gsub('\\.\\d+','',unique(diff$diff_testing$ENSEMBL)),
                                    columns=c("ENSEMBL","SYMBOL", "GENENAME", "ENTREZID"), keytype="ENSEMBL")
G3;'select()' returned 1:many mapping between keys and columns
g
library(ComplexHeatmap)

hm_maker <- function(markers, target, 
                     cdiff = diff, 
                     clabels = labels, 
                     remove = remove_leiden5){
  tib <- cdiff$diff_testing %>% 
    left_join(clabels, by = c('base'='leiden5')) %>% 
    left_join(conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) %>% 
    filter(SYMBOL %in% markers) %>% 
    mutate(base = as.character(base),
           base = paste0(base, ' - ', CT)) %>% 
    select(SYMBOL, base, logfoldchanges) %>% 
    pivot_wider(values_from = logfoldchanges, names_from = base)
  
  mat <- tib %>% select(-1) %>% as.matrix()
  row.names(mat) <- tib %>% pull(1)
  
  ha_column = ComplexHeatmap::HeatmapAnnotation(df = data.frame(Target = ifelse(grepl(target, colnames(tib)[-1]), "Target","Not"),
                                                                Remove = ifelse(str_extract(colnames(tib)[-1], '\\d+') %in% remove, "Remove","Retain")),        
                                                col = list(Target = c("Target" = "black","Not" = "white"),
                                                           Remove = c("Remove" = "red", "Retain" = "white")))
  
  col_fun = circlize::colorRamp2(c(-3, 0, 3), c("blue", "white", "red"))
  draw(Heatmap(mat, col=col_fun,
               name = 'logFoldChange',
               top_annotation = ha_column)
  )
}

Quick Check of Discrepancies when filtering out lower confidence scores

mct = obs$obs %>% group_by(leiden5, CT__sceiad_20250211_dev_mmGeneFilter) %>% count() %>% ungroup() %>% group_by(leiden5) %>% slice_max(order_by = n, n = 1)
filter_mct = obs$obs %>% filter(CT__sceiad_20250211_dev_mmGeneFilter__max_score > 0.9) %>% group_by(leiden5, CT__sceiad_20250211_dev_mmGeneFilter) %>% count() %>% ungroup() %>% group_by(leiden5) %>% slice_max(order_by = n, n = 1)
mct %>% left_join(filter_mct, by = 'leiden5') %>% filter(CT__sceiad_20250211_dev_mmGeneFilter.x != CT__sceiad_20250211_dev_mmGeneFilter.y)

CT Changes

ct_map <- c(
  '21' = 'cone (ml)',
  '61' = 'periocular mesenchyme',
  '62' = 'ciliary margin',
  '65' = 'rod (precursor)',
  '85' = 'lens',
  '77' = 'astrocyte',
  '13' = 'retinal ganglion',
  '44' = 'retinal ganglion',
  '51' = 'retinal ganglion',
  '47' = 'horizontal',
  '14' = 'neurogenic', # check above with score discrepancy 
  '87' = 'neurogenic', # check above with score discrepancy
  '70' = 'bipolar (precursor)' ) # check above with score discrepancy

labels <- obs$labels %>% 
  mutate(CT = ifelse(as.character(leiden5) %in% names(ct_map), ct_map[as.character(leiden5)], mCT_scvi))

remove_leiden5 <- c(43,74) # odd umap and marker expression (mixed ct)

Heatmaps

RPC / Neurogenic

markers <- c("HES1",
             "ZFP36L2",
             # "HES6",
             # "ATOH7",
             "VIM",
             "CCND1",
             "SFRP2",
             "SPP1",
             "ZFP36L1",
             "TF",
             "FOS",
             "TTYH1") %>% str_to_title()
mellough_markers <- read_csv("~/git/eyeMarkers/lists/rpc_markers__Mellough2019.csv")
Rows: 75 Columns: 2
── Column specification ──────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (2): HGNC, Cell Type

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
remove_leiden5 <- c()

markers <- mellough_markers %>% filter(`Cell Type` == 'RPC') %>% pull(HGNC) %>% str_to_title()
more <- c("PAX6","NEUROD1","ATOH7","HES6") %>% str_to_title()
hm_maker(c(markers, more), "rpc|neuro", clabels = obs$labels %>% mutate(CT = mCT))
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

Periocular Mesenchyme / Endo / Epi / Keratocyte

markers <- c("LUM","DCN","VIM","PDGFRA","COL1A2", # https://www.nature.com/articles/s42003-020-0922-4
             "MGP","MEG3","DCN","APOD","ANGPTL7","EFEMP1","BMP5","PRRX1")

markers <- #c("VIM","FAP","COL1A1","PDGFRB","S100A4", # fibro
  c("PENK", "PITX2", #POM
    "CDH5","VWF", # endo
    "MYF5", "USP18", #connective tissue
    "CDH1","KRT19","EPCAM", # epi
    "KERA",# keratocyte)
    "A2M") 
hm_maker(markers %>% str_to_title(), "periocu|endo|epi|kera",clabels = labels)
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

Lens

markers <- conv_table %>% filter(grepl("^Cry", SYMBOL)) %>% pull(SYMBOL)
hm_maker(markers %>% str_to_title(), "lens",clabels = labels)
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

RPE

markers <- c("Rpe65","Mitf","Best1")
hm_maker(markers %>% str_to_title(), "rpe",clabels = labels)
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

Astrocyte

markers <- c( "Gfap","Pax2")
hm_maker(markers %>% str_to_title(), "astroc",clabels = labels)
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

Mueller

markers <- c( "Glul","Clu","Apoe")
hm_maker(markers %>% str_to_title(), "astroc",clabels = labels)
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

Horizontal


markers <- c("LHX1","ONECUT1") %>% str_to_title()

hm_maker(markers, "hori")
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

Photoreceptors


markers <-  c('ARR3','OPN1LW','OPN1SW','RHO', 'OPN1MW', 'RCVRN',"CRX","PROM1","CNGA1","PDE6A") %>% str_to_title()
#markers <-  mellough_markers %>% filter(`Cell Type` %in% c('Rod','Cone')) %>% pull(HGNC)
hm_maker(markers, "rod|cone")
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

Bipolar

markers <- c("GRIK1","IRX6","LRTM1","PCP2","PRKCA","TRPM1","VSX1","VSX2") %>% str_to_title()
#markers <- mellough_markers %>% filter(`Cell Type` == 'Bipolar') %>% pull(HGNC)
hm_maker(markers, "bipolar")
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

Amacrine


markers <- c('GAD1','GAD2','SLC6A9','NFIA') %>% str_to_title()
markers <- mellough_markers %>% filter(`Cell Type` == 'Amacrine') %>% pull(HGNC) %>% str_to_title()
hm_maker(markers, "amacr")
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

Ganglion


markers <-  mellough_markers %>% filter(`Cell Type` == 'RGC') %>% pull(HGNC) %>% str_to_title()
hm_maker(markers, "ganglion")
Joining with `by = join_by(ENSEMBL)`
G2;H2;Warningh in left_join(., conv_table %>% select(SYMBOL, ENSEMBL) %>% unique()) :
  Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 234 of `x` matches multiple rows in `y`.
ℹ Row 296 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.g

Updated UMAP

nobs <- obs$obs %>% 
  left_join(labels, by = 'leiden5') %>% 
  filter(!leiden5 %in% remove_leiden5) 

# obs$obs %>% filter(leiden5 %in% c(14,57)) %>% mcHelpeRs::sum_rat(leiden5, CT__sceiad_20250211_dev_mmGeneFilter) 
# # tweaking for cluster 14 and 57 as these don't seem to be clearly bipolar precursors
# nobs <- nobs %>% mutate(CT = case_when(leiden5 == 14 & CT__sceiad_20250211_dev_mmGeneFilter == 'bipolar (precursor)' ~ 'rod (precursor)',
#                                        leiden5 == 14 & CT__sceiad_20250211_dev_mmGeneFilter == 'neurogenic' ~ 'neurogenic',
#                                        leiden5 == 14 & CT__sceiad_20250211_dev_mmGeneFilter == 'rod (precursor)' ~ 'rod (precursor)',
#                                        leiden5 == 14 & CT__sceiad_20250211_dev_mmGeneFilter == 'cone (precursor)' ~ 'rod (precursor)',
#                                        
#                                        leiden5 == 57 & CT__sceiad_20250211_dev_mmGeneFilter == 'bipolar (precursor)' ~ 'rod (precursor)',
#                                        leiden5 == 57 & CT__sceiad_20250211_dev_mmGeneFilter == 'rod (precursor)' ~ 'rod (precursor)',
#                                        leiden5 == 57 & CT__sceiad_20250211_dev_mmGeneFilter == 'neurogenic' ~ 'neurogenic',
#                                        leiden5 == 57 & CT__sceiad_20250211_dev_mmGeneFilter == 'bipolar' ~ 'bipolar',
#                                        leiden5 == 57 ~ 'neurogenic',
#                                        TRUE ~ CT
# ))
nobs %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CT), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(CT) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = CT, color = CT)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("CT")  

Stage 3

Output updated CT calls, update the h5ad, and re-run scVI

set.seed(2025-03-05)
ref <- nobs %>% 
  filter(scANVI_MCT_max_score > 0.9) |> 
  group_by(study_accession, CT) %>% 
  slice_sample(n = 1000, replace = TRUE) %>% 
  unique()

query <- nobs %>% filter(!barcodei %in% ref$barcodei)


# ref$barcodei %>% write(gzfile('~/git/scEiaD_modeling/data/mm111_dev_eye_ref_bcs.20250305.stage3.csv.gz'))
# query$barcodei %>% write(gzfile('~/git/scEiaD_modeling/data/mm111_dev_eye_query_bcs.20250305.stage3.csv.gz'))
# #
# nobs %>% dplyr::rename(barcode = barcodei) %>% write_csv('~/git/scEiaD_modeling/data/Mouse_Developing_Eye__stage3_CTcalls.freeze20250305.01.csv.gz')
cd /data/OGVFB_BG/scEiaD/2024_02_28/snakeout/hs111_developing_eye/stage4
source /data/$USER/conda/etc/profile.d/conda.sh && source /data/$USER/conda/etc/profile.d/mamba.sh
mamba activate rapids_singlecell

python ~/git/scEiaD_modeling/workflow/scripts/append_obs.py ../../mm111.adata.solo.20250131.h5ad /home/mcgaugheyd/git/scEiaD_modeling/data/Mouse_Developing_Eye__stage3_CTcalls.freeze20250305.01.csv.gz  mm111.adata.solo.20250305.dev.stage3.h5ad --transfer_columns CT

obs s3

obs_s3 <- pull_obs('~/data/scEiaD_modeling/mm111_developing_eye/stage3/mm111_dev_eye_20250305_stage3_2000hvg_200e_30l.obs.csv.gz', label = 'CT', machine_label = 'scANVI_CT', cluster_col = 'leiden5')
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.

obs_s3$obs %>%  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CT), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(CT) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = CT, color = CT)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("CT")  




obs_s3$obs %>%
  left_join(obs_s3$labels, by = 'leiden5') %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = as.factor(leiden5)), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_text_repel(data = . %>% group_by(mCT, leiden5) %>%
                             summarise(umap1 = median(umap1),
                                       umap2 = median(umap2),),
                           aes(label = paste0(mCT,'-',leiden5),color = as.factor(leiden5)), bg.color = 'white') +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey(), pals::alphabet(), pals::kelly(), pals::brewer.set1(10), pals::okabe(), pals::watlington()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("leiden5") 

Confusion Matrix

machine_label = 'scANVI_CT'; label = 'CT'
obs_s3$obs %>% 
  #filter(scANVI_CT_max_score > 0.9) %>% 
  group_by(.data[[label]],.data[[machine_label]]) %>% 
  summarise(Count = n()) %>% 
  mutate(Ratio = Count/sum(Count)) %>% 
  ggplot(aes(x=.data[[label]],y=.data[[machine_label]],fill=Ratio, label = round(Ratio, 2))) + 
  geom_tile() + 
  shadowtext::geom_shadowtext() + 
  cowplot::theme_cowplot() +
  scale_fill_viridis_c(begin = 0, end = 1) +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) 
`summarise()` has grouped output by 'CT'. You can override using the `.groups` argument.

# retains CT calls >= 0.05 of a cluster. anything below gets changed to the dominant ct
nobs_s3_cleaning <- obs_s3$obs %>% 
  group_by(leiden5, scANVI_CT) %>% 
  summarise(Count = n()) %>%
  mutate(Ratio = Count / sum(Count)) %>% 
  mutate(dominant_celltype = scANVI_CT[which.max(Count)]) %>% 
  mutate(CTc = case_when(Ratio < 0.05 ~ dominant_celltype,
                         TRUE ~ scANVI_CT))
`summarise()` has grouped output by 'leiden5'. You can override using the `.groups` argument.
nobs_s3 <- obs_s3$obs %>% 
  left_join(nobs_s3_cleaning %>% 
              dplyr::select(leiden5, scANVI_CT, CTc), by = c("leiden5","scANVI_CT"))
nobs_s3 <- nobs_s3 %>% select(-umap1, -umap2) %>% left_join(obs$obs %>% dplyr::select(umap1,umap2, barcodei), by = 'barcodei')

nobs_s3 %>% mcHelpeRs::sum_rat(CT, CTc,threshold = 0.01)

Final UMAP


nobs_s3 %>%  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CTc), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(CTc) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = CTc, color = CTc)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("CTc")  

“Transitional”

Plot of the ML cell type call confidence (1 is 100% confidence). We see that in several “precursor” positions in the UMAP the confidence is much lower.

nobs_s3 %>%  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = scANVI_CT_max_score), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(CTc) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = CTc), color = 'black') +
  scale_color_viridis_c(option = 'rocket') + 
  cowplot::theme_cowplot() + 
  ggtitle("CTc")  

Output

sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: aarch64-apple-darwin20
Running under: macOS Sonoma 14.7.4

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] ComplexHeatmap_2.20.0 ggtree_3.12.0         fstcore_0.9.18        lubridate_1.9.3       forcats_1.0.0        
 [6] stringr_1.5.1         dplyr_1.1.4           purrr_1.0.2           readr_2.1.5           tidyr_1.3.1          
[11] tibble_3.2.1          ggplot2_3.5.1         tidyverse_2.0.0      

loaded via a namespace (and not attached):
  [1] RColorBrewer_1.1-3          shape_1.4.6.1               rstudioapi_0.16.0           jsonlite_1.8.8             
  [5] magrittr_2.0.3              magick_2.8.5                farver_2.1.2                rmarkdown_2.27             
  [9] GlobalOptions_0.1.2         fs_1.6.4                    zlibbioc_1.50.0             vctrs_0.6.5                
 [13] Cairo_1.6-2                 memoise_2.0.1               DelayedMatrixStats_1.26.0   htmltools_0.5.8.1          
 [17] S4Arrays_1.4.1              BiocNeighbors_1.22.0        SparseArray_1.4.8           gridGraphics_0.5-1         
 [21] sass_0.4.9                  bslib_0.8.0                 htmlwidgets_1.6.4           cachem_1.1.0               
 [25] igraph_2.0.3                iterators_1.0.14            lifecycle_1.0.4             pkgconfig_2.0.3            
 [29] rsvd_1.0.5                  Matrix_1.7-0                R6_2.5.1                    fastmap_1.2.0              
 [33] clue_0.3-65                 GenomeInfoDbData_1.2.12     MatrixGenerics_1.16.0       digest_0.6.36              
 [37] aplot_0.2.3                 colorspace_2.1-1            AnnotationDbi_1.66.0        patchwork_1.2.0            
 [41] S4Vectors_0.42.1            dqrng_0.4.1                 irlba_2.3.5.1               RSQLite_2.3.7              
 [45] crosstalk_1.2.1             GenomicRanges_1.56.1        org.Hs.eg.db_3.19.1         beachmat_2.20.0            
 [49] labeling_0.4.3              org.Mm.eg.db_3.19.1         fansi_1.0.6                 timechange_0.3.0           
 [53] httr_1.4.7                  abind_1.4-5                 compiler_4.4.1              doParallel_1.0.17          
 [57] bit64_4.0.5                 withr_3.0.0                 BiocParallel_1.38.0         DBI_1.2.3                  
 [61] R.utils_2.12.3              maps_3.4.2                  DelayedArray_0.30.1         rjson_0.2.21               
 [65] bluster_1.14.0              tools_4.4.1                 ape_5.8                     fst_0.9.8                  
 [69] R.oo_1.26.0                 mcHelpeRs_0.0.2             glue_1.7.0                  nlme_3.1-165               
 [73] shadowtext_0.1.4            cluster_2.1.6               generics_0.1.3              gtable_0.3.5               
 [77] tzdb_0.4.0                  R.methodsS3_1.8.2           data.table_1.16.99          hms_1.1.3                  
 [81] BiocSingular_1.20.0         ScaledMatrix_1.12.0         metapod_1.12.0              utf8_1.2.4                 
 [85] XVector_0.44.0              BiocGenerics_0.50.0         foreach_1.5.2               ggrepel_0.9.5              
 [89] pillar_1.9.0                vroom_1.6.5                 yulab.utils_0.1.5           limma_3.60.4               
 [93] pals_1.9                    circlize_0.4.16             treeio_1.28.0               lattice_0.22-6             
 [97] bit_4.0.5                   tidyselect_1.2.1            SingleCellExperiment_1.26.0 locfit_1.5-9.10            
[101] Biostrings_2.72.1           scuttle_1.14.0              knitr_1.48                  IRanges_2.38.1             
[105] edgeR_4.2.1                 SummarizedExperiment_1.34.0 scattermore_1.2             stats4_4.4.1               
[109] xfun_0.48                   Biobase_2.64.0              statmod_1.5.0               matrixStats_1.3.0          
[113] DT_0.33                     stringi_1.8.4               UCSC.utils_1.0.0            lazyeval_0.2.2             
[117] ggfun_0.1.5                 yaml_2.3.10                 evaluate_0.24.0             codetools_0.2-20           
[121] ggplotify_0.1.2             cli_3.6.3                   munsell_0.5.1               jquerylib_0.1.4            
[125] dichromat_2.0-0.1           Rcpp_1.0.13                 GenomeInfoDb_1.40.1         mapproj_1.2.11             
[129] png_0.1-8                   parallel_4.4.1              blob_1.2.4                  scran_1.32.0               
[133] sparseMatrixStats_1.16.0    viridisLite_0.4.2           tidytree_0.4.6              metamoRph_0.2.1            
[137] ggridges_0.5.6              scales_1.3.0                crayon_1.5.3                GetoptLong_1.0.5           
[141] rlang_1.1.4                 KEGGREST_1.44.1             cowplot_1.1.3              
LS0tCnRpdGxlOiAiTW91c2UgRGV2ZWxvcG1lbnQgRXllIENyZWF0aW9uIgpvdXRwdXQ6CiBodG1sX25vdGVib29rOgogIGF1dGhvcjogIkRhdmlkIE1jR2F1Z2hleSIKICBkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCiAgdGhlbWU6IGZsYXRseQogIHRvYzogdHJ1ZQogIHRvY19mbG9hdDogdHJ1ZQogIGNvZGVfZm9sZGluZzogc2hvdwotLS0KCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBtZXNzYWdlID0gRkFMU0UsICB3YXJuaW5nID0gRkFMU0UsCiAgY29sbGFwc2UgPSBUUlVFLAogIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gOCwKICBjb21tZW50ID0gIiM+IiwKICBkcGk9MzAwCikKYGBgCiMgU3RhZ2UgMQpQdWxsIGluIG1vdXNlIGNlbGwgbWV0YWRhdGEgYW5kIHJldGFpbiBleWUgY2VsbHMuCgpPdXRwdXQgdGhlIG1vdXNlIG9jdWxhciBiYXJjb2Rlcy4KCmBgYHtyLCBleGVjID0gRkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpzYW1wbGVfbWV0YSA8LSBkYXRhLnRhYmxlOjpmcmVhZCgnfi9naXQvc2NFaWFEX3F1YW50L3NhbXBsZV9tZXRhLnNjRWlhRF92MS4yMDI1XzAyXzAzLjAyLnRzdi5neicpCmNlbGxfbWV0YSA8LSBkYXRhLnRhYmxlOjpmcmVhZCgnfi9kYXRhL3NjRWlhRF9tb2RlbGluZy9tbTExMS5hZGF0YS5zb2xvLjIwMjUwMTMxLm9icy5jc3YuZ3onKVssLTFdICU+JSAKICByZWxvY2F0ZShiYXJjb2RlKSAlPiUgCiAgZmlsdGVyKHNvbG9fZG91YmxldCA9PSAiRkFMU0UiKQoKbW0xMTFfZXllIDwtIGNlbGxfbWV0YSAlPiUgCiAgZmlsdGVyKAogICAgb3JnYW4gPT0gJ0V5ZScsCiAgICBvcmdhbmlzbSA9PSAnTXVzIG11c2N1bHVzJywKICAgICFncmVwbCgiXiMiLCBzYW1wbGVfYWNjZXNzaW9uKSwKICAgIHNvdXJjZSA9PSAnVGlzc3VlJykKCiNtbTExMV9leWUkYmFyY29kZSAlPiUgd3JpdGUoZ3pmaWxlKCd+L2dpdC9zY0VpYURfbW9kZWxpbmcvZGF0YS9tbTExMV9leWVfYmNzLjIwMjUwMjIxLmNzdi5neicpKQpgYGAKCmBgYHtiMiBjdC1wcmVkaWN0aW9uLCBldmFsID0gRkFMU0V9CnNpbnRlcmFjdGl2ZSAtLW1lbT0xMjhHIC0tdGltZT04OjAwOjAwICAtLWdyZXM9Z3B1OmExMDA6MQpjZCAvZGF0YS9PR1ZGQl9CRy9zY0VpYUQvMjAyNF8wMl8yOC9zbmFrZW91dC9tbTExMV9kZXZlbG9waW5nX2V5ZQpzb3VyY2UgL2RhdGEvJFVTRVIvY29uZGEvZXRjL3Byb2ZpbGUuZC9jb25kYS5zaCAmJiBzb3VyY2UgL2RhdGEvJFVTRVIvY29uZGEvZXRjL3Byb2ZpbGUuZC9tYW1iYS5zaAptYW1iYSBhY3RpdmF0ZSByYXBpZHNfc2luZ2xlY2VsbApiYXNoIGN0X3Byb2plY3Rpb25fY2FsbC5zaApgYGAKCgoKIyMgc2NhbnZpIE1vZGVsaW5nCiMjIyBzY0VpYUQgSHVtYW4gRGV2ZWxvcGluZyBNb2RlbApgYGB7cn0Kc2NlaWFkX21ldGEgPC0gZnN0OjpyZWFkX2ZzdCgnfi9kYXRhL3NjRWlhRF8yMDIyXzAyL21ldGFfZmlsdGVyLmZzdCcpCmN0cF9tbTExMV9faHVtYW5fbW9kZWxpbmcgPC0gZGF0YS50YWJsZTo6ZnJlYWQoJy9Vc2Vycy9tY2dhdWdoZXlkL2RhdGEvc2NFaWFEX21vZGVsaW5nL21tMTExX2RldmVsb3BpbmdfZXllL21tMTExLmV5ZS5odW1hbl9DVF9wcm9qZWN0aW9uc18yMDEyMDEyOC5jc3YuZ3onKSAlPiUgc2VsZWN0KC0xNykgJT4lIGxlZnRfam9pbihzY2VpYWRfbWV0YSAlPiUgc2VsZWN0KGJhcmNvZGUgPSBCYXJjb2RlLCBDZWxsVHlwZV9wcmVkaWN0KSwgYnkgPSAnYmFyY29kZScpICU+JSAKICAjIG9ubHkgbG9vayBhdCA+PSAxMCBkYXkgY2VsbHMKICBtdXRhdGUoYWdlID0gYXMubnVtZXJpYyhhZ2UpKSAlPiUgCiAgZmlsdGVyKGFnZSA8IDEwKQoKbGFiZWwgPC0gJ01ham9yQ2VsbFR5cGUnCm1hY2hpbmVfbGFiZWwgPC0gJ0NUX19zY2VpYWRfMjAyNTAyMTFfZGV2X21tR2VuZUZpbHRlcicKbW9zdF9jb21tb25fbWlzbGFiZWwgPC0gY3RwX21tMTExX19odW1hbl9tb2RlbGluZyAlPiUgCiAgZ3JvdXBfYnkoLmRhdGFbW2xhYmVsXV0sLmRhdGFbW21hY2hpbmVfbGFiZWxdXSkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lIAogIGFycmFuZ2UoLmRhdGFbW2xhYmVsXV0sIC1Db3VudCkgJT4lIAogIHNsaWNlX21heChvcmRlcl9ieSA9IENvdW50LCBuID0gMikgJT4lIAogIGZpbHRlciguZGF0YVtbbGFiZWxdXSAhPSAuZGF0YVtbbWFjaGluZV9sYWJlbF1dKQoKY3RwX21tMTExX19odW1hbl9tb2RlbGluZyAlPiUgCiAgbXV0YXRlKHRmID0gY2FzZV93aGVuKC5kYXRhW1tsYWJlbF1dID09IC5kYXRhW1ttYWNoaW5lX2xhYmVsXV0gfiBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gRkFMU0UpKSAlPiUgCiAgZ3JvdXBfYnkoLmRhdGFbW2xhYmVsXV0pICU+JSAKICBzdW1tYXJpc2UoYWNjdXJhY3kgPSBzdW0odGYpL2xlbmd0aCh0ZikpICU+JSAKICBhcnJhbmdlKC5kYXRhW1tsYWJlbF1dKSAlPiUgCiAgbGVmdF9qb2luKG1vc3RfY29tbW9uX21pc2xhYmVsICU+JSAKICAgICAgICAgICAgICBzZWxlY3QoYW55X29mKGxhYmVsKSwgCiAgICAgICAgICAgICAgICAgICAgIGBtb3N0IGNvbW1vbiBtaXNsYWJlbGAgPSBhbnlfb2YobWFjaGluZV9sYWJlbCksCiAgICAgICAgICAgICAgICAgICAgIGBtaXNsYWJlbCBjb3VudGAgPSBDb3VudCkpCmBgYAoKCgoKIyMjIyBVTUFQCmBgYHtyLCBmaWcud2lkdGg9MTIsZmlnLmhlaWdodD0xMn0KdW1hcDEgPSAndW1hcDFfc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXInCnVtYXAyID0gJ3VtYXAyX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyJwpjdCA9ICdDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXInCmNvbG9yID0gJ0NUX19zY2VpYWRfMjAyNTAyMTFfZGV2X21tR2VuZUZpbHRlcicKc2NvcmUgPC0gJ0NUX19zY2VpYWRfMjAyNTAyMTFfZGV2X21tR2VuZUZpbHRlcl9fbWF4X3Njb3JlJwp1bWFwX2N0X3Bsb3R0ZXIgPC0gZnVuY3Rpb24ob2JqLCB1bWFwMSwgdW1hcDIsIGN0LCBjb2xvciwgbGFiZWxzID0gVFJVRSl7CiAgcGxvdCA8LSBvYmogJT4lIAogICAgbXV0YXRlKGxlaWRlbiA9IGFzLmZhY3RvcihsZWlkZW4pKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHg9LmRhdGFbW3VtYXAxXV0seT0uZGF0YVtbdW1hcDJdXSkpICsKICAgIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IC5kYXRhW1tjb2xvcl1dKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSwgcGFsczo6a2VsbHkoKSwgcGFsczo6YWxwaGFiZXQoKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICB1bm5hbWUoKSkgKyAKICAgIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKICBpZiAobGFiZWxzKXsKICAgIHBsb3QgPC0gcGxvdCArIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IC4gJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KC5kYXRhW1tjdF1dKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKGFjcm9zcyhhbGxfb2YoYyh1bWFwMSx1bWFwMikpLCBtZWRpYW4pKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gLmRhdGFbW2N0XV0sIGNvbG9yID0gLmRhdGFbW2NvbG9yXV0pKSAKICAgIAogIH0gCiAgcHJpbnQocGxvdCkKfQoKdW1hcF9zY29yZV9wbG90dGVyIDwtIGZ1bmN0aW9uKG9iaiwgdW1hcDEsIHVtYXAyLCBjdCwgc2NvcmUpewogIG9iaiAlPiUgCiAgICBnZ3Bsb3QoYWVzKHg9LmRhdGFbW3VtYXAxXV0seT0uZGF0YVtbdW1hcDJdXSkpICsKICAgIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IC5kYXRhW1tzY29yZV1dKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogICAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoLmRhdGFbW2N0XV0pICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UoYWNyb3NzKGFsbF9vZihjKHVtYXAxLHVtYXAyKSksIG1lZGlhbikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSAuZGF0YVtbY3RdXSksIG1heC5vdmVybGFwcyA9IEluZikgKyAKICAgIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICAgIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKfQp1bWFwX2N0X3Bsb3R0ZXIoY3RwX21tMTExX19odW1hbl9tb2RlbGluZywgdW1hcDEsIHVtYXAyLCBjdCwgY29sb3IpCnVtYXBfY3RfcGxvdHRlcihjdHBfbW0xMTFfX2h1bWFuX21vZGVsaW5nLCB1bWFwMSwgdW1hcDIsICdsZWlkZW4nLCAnbGVpZGVuJykKdW1hcF9zY29yZV9wbG90dGVyKGN0cF9tbTExMV9faHVtYW5fbW9kZWxpbmcsIHVtYXAxLCB1bWFwMiwgY3QsIHNjb3JlKQpgYGAKCgojIyBNYWpvckNlbGxUeXBlIDwtPiBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIKTG9va3MgcXVpdGUgZ29vZCAtIHBlcmhhcHMgb25seSBwZXJpY3l0ZSBhbmQgY2lsaWFyeSBtYXJnaW4gKG5laXRoZXIgb2Ygd2hpY2ggYXJlIGluIHRoZSBiaWcgbW9kZWwpIGFyZSB3cm9uZz8KYGBge3J9CmN0cF9tbTExMV9faHVtYW5fbW9kZWxpbmcgJT4lIAogIG1jSGVscGVSczo6c3VtX3JhdCgnQ2VsbFR5cGVfcHJlZGljdCcsICdNYWpvckNlbGxUeXBlJywgJ0NUX19zY2VpYWRfMjAyNTAyMTFfZGV2X21tR2VuZUZpbHRlcicsIHRocmVzaG9sZCA9IDAuMDUpICU+JSAKICAjZmlsdGVyKE1ham9yQ2VsbFR5cGUgIT0gJycpICU+JSAKICBEVDo6ZGF0YXRhYmxlKCkKYGBgCiMjIFJvdWdoIHNldCBDVCBjYWxscyBmb3Igc2NWSSBtb2RlbGluZwpBcyBhIGNydWNpYWwgc3RlcCBpcyBzdWJzZXR0aW5nIG9mIGVhY2ggQ1QgdG8gcHJldmVudCBhIHZlcnkgaGlnaCBuIHNldCAoZS5nLiByb2RzKSB0byBkb21pbmF0ZQpgYGB7cn0KY3RwX21tMTExX19odW1hbl9tb2RlbGluZyA8LSBjdHBfbW0xMTFfX2h1bWFuX21vZGVsaW5nICU+JSAKICBtdXRhdGUocm91Z2hfY3QgPSBjYXNlX3doZW4oQ2VsbFR5cGVfcHJlZGljdCAlaW4lIGMoIkNpbGlhcnkgTWFyZ2luIiwgIlBlcmljeXRlIikgfiB0b2xvd2VyKENlbGxUeXBlX3ByZWRpY3QpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gQ1RfX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyKSkKYGBgCgojIFN0YWdlIDIKUGljayByZWYgYmFyY29kZXMgdG8gbWFrZSBzY1ZJIG1vZGVscwoKYGBge3J9CnNldC5zZWVkKDIwMjUwMzAzKQprZWVwX2N0IDwtIGN0cF9tbTExMV9faHVtYW5fbW9kZWxpbmcgJT4lIGdyb3VwX2J5KHJvdWdoX2N0KSAlPiUgc3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUgZmlsdGVyKENvdW50ID4gMTApICU+JSBwdWxsKHJvdWdoX2N0KSAlPiUgdW5pcXVlKCkgJT4lIHNvcnQoKQoKCm1tMTExX2Rldl9leWVfcmVmX2JjcyA8LSBjdHBfbW0xMTFfX2h1bWFuX21vZGVsaW5nICU+JSB1bmdyb3VwKCkgJT4lIAogIGZpbHRlcihyb3VnaF9jdCAlaW4lIGtlZXBfY3QpICU+JSAKICBncm91cF9ieShzdHVkeV9hY2Nlc3Npb24sIHJvdWdoX2N0KSAlPiUgCiAgc2FtcGxlX24oMTAwMCwgcmVwbGFjZSA9IFRSVUUpICU+JSAKICB1bmlxdWUoKQoKbW0xMTFfZGV2X2V5ZV9xdWVyeV9iY3MgPC0gY3RwX21tMTExX19odW1hbl9tb2RlbGluZyAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBmaWx0ZXIoIWJhcmNvZGUgJWluJSBtbTExMV9kZXZfZXllX3JlZl9iY3MkYmFyY29kZSkKCiNtbTExMV9kZXZfZXllX3JlZl9iY3MkYmFyY29kZSAlPiUgd3JpdGUoZ3pmaWxlKCd+L2dpdC9zY0VpYURfbW9kZWxpbmcvZGF0YS9tbTExMV9kZXZfZXllX3JlZl9iY3MuMjAyNTAzMDMuY3N2Lmd6JykpCiNtbTExMV9kZXZfZXllX3F1ZXJ5X2JjcyRiYXJjb2RlICU+JSB3cml0ZShnemZpbGUoJ34vZ2l0L3NjRWlhRF9tb2RlbGluZy9kYXRhL21tMTExX2Rldl9leWVfcXVlcnlfYmNzLjIwMjUwMzAzLmNzdi5neicpKQpgYGAKCmBgYHtiYXNoIGIyIHNjdmkgdGltZSwgZXZhbCA9IEZBTFNFfQpjZCAvZGF0YS9PR1ZGQl9CRy9zY0VpYUQvMjAyNF8wMl8yOC9zbmFrZW91dC9tbTExMV9kZXZlbG9waW5nX2V5ZQpzYmF0Y2ggLS10aW1lPTEwOjAwOjAwIHNuYWtlY2FsbC5zaApgYGAKCmBgYHtyfQpzb3VyY2UoJ2FuYWx5c2lzX3NjcmlwdHMuUicpCm9icyA8LSBwdWxsX29icygnfi9kYXRhL3NjRWlhRF9tb2RlbGluZy9tbTExMV9kZXZlbG9waW5nX2V5ZS9tbTExMV9kZXZfZXllXzIwMjUwMzAzXzIwMDBodmdfNTBlXzMwbC5vYnMuY3N2Lmd6JywgbWFjaGluZV9sYWJlbCA9ICdzY0FOVklfTUNUJywgY2x1c3Rlcl9jb2wgPSAnbGVpZGVuNScpCgpvYnMkb2JzIDwtIG9icyRvYnMgJT4lIGxlZnRfam9pbihjdHBfbW0xMTFfX2h1bWFuX21vZGVsaW5nICU+JSBzZWxlY3QoYmFyY29kZSwgQ1RfX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyLCBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXJfX21heF9zY29yZSwgIHVtYXAxX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyLHVtYXAyX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyKSwgYnkgPSBjKCJiYXJjb2RlaSIgPSAnYmFyY29kZScpKQoKb2JzJGxhYmVscyRtQ1Rfc2N2aSA8LSBvYnMkb2JzICU+JSBtY0hlbHBlUnM6OnN1bV9yYXQoImxlaWRlbjUiLCJDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIiKSAlPiUgc2xpY2VfbWF4KG9yZGVyX2J5ID0gQ291bnQsIG4gPSAxKSAlPiUgcHVsbChDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIpCgpvYnMkbGFiZWxzJG1NQ1Rfc2N2aSA8LSBvYnMkb2JzICU+JSBtY0hlbHBlUnM6OnN1bV9yYXQoImxlaWRlbjUiLCJDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIiKSAlPiUgc3VtbWFyaXNlKG91dCA9IHBhc3RlMChDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIsIGNvbGxhcHNlID0gJywgJykpICU+JSBwdWxsKG91dCkKYGBgCiMjIFVNQVBzCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9Cm9icyRvYnMgJT4lIAogIGxlZnRfam9pbihvYnMkbGFiZWxzLCBieSA9ICdsZWlkZW41JykgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IHNjQU5WSV9NQ1QpLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoc2NBTlZJX01DVCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodW1hcDEgPSBtZWRpYW4odW1hcDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IHNjQU5WSV9NQ1QsIGNvbG9yID0gc2NBTlZJX01DVCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZ2d0aXRsZSgiTWFqb3JDZWxsVHlwZSAoc2NBTlZJKSIpCgpvYnMkb2JzICU+JSAKICBsZWZ0X2pvaW4ob2JzJGxhYmVscywgYnkgPSAnbGVpZGVuNScpICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIpLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoQ1RfX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gQ1RfX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyLCBjb2xvciA9IENUX19zY2VpYWRfMjAyNTAyMTFfZGV2X21tR2VuZUZpbHRlcikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZ2d0aXRsZSgiQ1RfX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyIikgIAoKb2JzJG9icyAlPiUgCiAgbGVmdF9qb2luKG9icyRsYWJlbHMsIGJ5ID0gJ2xlaWRlbjUnKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gQ1RfX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCkpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBnZ3RpdGxlKCJDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIiKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC0yMCwyMCksIHlsaW0gPSBjKC0xNSwxNSkpICsKICBmYWNldF93cmFwKH5zdHVkeV9hY2Nlc3Npb24pCgpvYnMkb2JzICU+JSAKICBsZWZ0X2pvaW4ob2JzJGxhYmVscywgYnkgPSAnbGVpZGVuNScpICU+JSAKICBtdXRhdGUoYWdlID0gYXMuaW50ZWdlcihhZ2UpKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gYWdlKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogICMgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoQ1RfX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyKSAlPiUgCiAgIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAjICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAjICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gQ1RfX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyKSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhvcHRpb24gPSAnSCcpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsKICBnZ3RpdGxlKCJDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIiKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC0yMCwyMCksIHlsaW0gPSBjKC0xNSwxNSkpCmBgYAoKIyMgbGVpZGVuNSBVTUFQCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9Cm9icyRvYnMgJT4lCiAgbGVmdF9qb2luKG9icyRsYWJlbHMsIGJ5ID0gJ2xlaWRlbjUnKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gYXMuZmFjdG9yKGxlaWRlbjUpKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkobUNUX3NjdmksIGxlaWRlbjUpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVtYXAyID0gbWVkaWFuKHVtYXAyKSwpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSBwYXN0ZTAobUNUX3NjdmksJy0nLGxlaWRlbjUpLGNvbG9yID0gYXMuZmFjdG9yKGxlaWRlbjUpKSwgYmcuY29sb3IgPSAnd2hpdGUnKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSwgcGFsczo6YWxwaGFiZXQoKSwgcGFsczo6a2VsbHkoKSkgJT4lIHVubmFtZSgpKSArIAogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGdndGl0bGUoImxlaWRlbjUiKSAKYGBgCgoKIyMgaGNsdXN0CmBgYHtyLCBmaWcud2lkdGggPSAxOCwgZmlnLmhlaWdodCA9IDEwfQpwYiA8LSBkYXRhLnRhYmxlOjpmcmVhZCgnL1VzZXJzL21jZ2F1Z2hleWQvZGF0YS9zY0VpYURfbW9kZWxpbmcvbW0xMTFfZGV2ZWxvcGluZ19leWUvbW0xMTFfZGV2X2V5ZV8yMDI1MDMwM18yMDAwaHZnXzUwZV8zMGwucHNldWRvQnVsay5sZWlkZW41LmNzdi5neicpCmNvbG5hbWVzKHBiKSA8LSBnc3ViKCJcXC5cXGQrIiwiIixjb2xuYW1lcyhwYikpCmh2ZyA8LSBkYXRhLnRhYmxlOjpmcmVhZCgnL1VzZXJzL21jZ2F1Z2hleWQvZGF0YS9zY0VpYURfbW9kZWxpbmcvbW0xMTFfZGV2ZWxvcGluZ19leWUvaHZnMjAwMC5jc3YuZ3onKVstMSxdCnJuYW1lcyA8LSBwYiRWMQpjbHVzdCA8LSBzdHJfZXh0cmFjdChybmFtZXMsICdcXGQrJykgJT4lIGFzLmludGVnZXIoKQpwYiA8LSBwYlssLTFdICU+JSBhcy5tYXRyaXgoKQpyb3cubmFtZXMocGIpIDwtIGFzLmNoYXJhY3RlcihjbHVzdCkKcGIgPC0gcGJbYXMuY2hhcmFjdGVyKG9icyRsYWJlbHMkbGVpZGVuNSksXQoKcGJfbm9ybSA8LSBtZXRhbW9ScGg6Om5vcm1hbGl6ZV9kYXRhKHQocGIpLCBzYW1wbGVfc2NhbGUgPSAnY3BtJykgJT4lIHQoKSAKCnBiX2Z1bGwgPC0gcGJfbm9ybQpwYl9ub3JtIDwtIHBiX25vcm1bLGh2ZyRWMl0KCiNwYl9ub3JtIDwtIHBiX25vcm1bLGh2ZyRWMlshaHZnJFYyICVpbiUgYyhjY19nZW5lcyxyaWJvX2dlbmVzKV1dCiMgaHR0cHM6Ly9zdGF0cy5zdGFja2V4Y2hhbmdlLmNvbS9xdWVzdGlvbnMvMzE1NjUvY29tcHV0ZS1hLWNvc2luZS1kaXNzaW1pbGFyaXR5LW1hdHJpeC1pbi1yCnNpbSA8LSBwYl9ub3JtIC8gc3FydChyb3dTdW1zKHBiX25vcm0gKiBwYl9ub3JtKSkKc2ltIDwtIHNpbSAlKiUgdChzaW0pCkRfc2ltIDwtIGFzLmRpc3QoMSAtIHNpbSkKCmhjbHVzdF9zaW0gPC0gaGNsdXN0KERfc2ltLCBtZXRob2QgPSAnYXZlcmFnZScpCgpoY2x1c3Rfc2ltJGxhYmVscyA8LSBvYnMkbGFiZWxzICU+JSBwdWxsKGxlaWRlbjUpCgpsaWJyYXJ5KGdndHJlZSkKcCA8LSBnZ3RyZWUoaGNsdXN0X3NpbSkKCnAkZGF0YSA8LSBwJGRhdGEgJT4lIGxlZnRfam9pbihvYnMkbGFiZWwsIGJ5ID0gYygibGFiZWwiID0gImxlaWRlbjUiKSkKcCArIGxheW91dF9kZW5kcm9ncmFtKCkgKwogIGdlb21fdGlwbGFiKGFlcyhsYWJlbCA9IHBhc3RlKGxhYmVsLCBtQ1Rfc2N2aSwgc2VwID0gJyAtICcpLCBjb2xvciA9IG1DVF9zY3ZpKSkgKyAKICB0aGVtZV9kZW5kcm9ncmFtKHBsb3QubWFyZ2luPW1hcmdpbigxNiwxNiwzMDAsMTYpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArIAogIGd1aWRlcyhjb2xvcj0ibm9uZSIpCmBgYAoKIyBDVCBieSBDVApgYGB7cn0KZGlmZiA8LSBwdWxsX2RpZmYoIn4vZGF0YS9zY0VpYURfbW9kZWxpbmcvbW0xMTFfZGV2ZWxvcGluZ19leWUvbW0xMTFfZGV2X2V5ZV8yMDI1MDMwM18yMDAwaHZnXzUwZV8zMGwuZGlmZnRlc3RpbmcubGVpZGVuNS5jc3YuZ3oiLCBvcmdkYiA9ICBvcmcuTW0uZWcuZGI6Om9yZy5NbS5lZy5kYiwgY2x1c3Rlcl9jb2wgPSAnbGVpZGVuNScpCgpjb252X3RhYmxlIDwtIEFubm90YXRpb25EYmk6OnNlbGVjdChvcmcuTW0uZWcuZGI6Om9yZy5NbS5lZy5kYiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5cz1nc3ViKCdcXC5cXGQrJywnJyx1bmlxdWUoZGlmZiRkaWZmX3Rlc3RpbmckRU5TRU1CTCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5zPWMoIkVOU0VNQkwiLCJTWU1CT0wiLCAiR0VORU5BTUUiLCAiRU5UUkVaSUQiKSwga2V5dHlwZT0iRU5TRU1CTCIpCgpgYGAKCgoKYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD01fQpsaWJyYXJ5KENvbXBsZXhIZWF0bWFwKQoKaG1fbWFrZXIgPC0gZnVuY3Rpb24obWFya2VycywgdGFyZ2V0LCAKICAgICAgICAgICAgICAgICAgICAgY2RpZmYgPSBkaWZmLCAKICAgICAgICAgICAgICAgICAgICAgY2xhYmVscyA9IGxhYmVscywgCiAgICAgICAgICAgICAgICAgICAgIHJlbW92ZSA9IHJlbW92ZV9sZWlkZW41KXsKICB0aWIgPC0gY2RpZmYkZGlmZl90ZXN0aW5nICU+JSAKICAgIGxlZnRfam9pbihjbGFiZWxzLCBieSA9IGMoJ2Jhc2UnPSdsZWlkZW41JykpICU+JSAKICAgIGxlZnRfam9pbihjb252X3RhYmxlICU+JSBzZWxlY3QoU1lNQk9MLCBFTlNFTUJMKSAlPiUgdW5pcXVlKCkpICU+JSAKICAgIGZpbHRlcihTWU1CT0wgJWluJSBtYXJrZXJzKSAlPiUgCiAgICBtdXRhdGUoYmFzZSA9IGFzLmNoYXJhY3RlcihiYXNlKSwKICAgICAgICAgICBiYXNlID0gcGFzdGUwKGJhc2UsICcgLSAnLCBDVCkpICU+JSAKICAgIHNlbGVjdChTWU1CT0wsIGJhc2UsIGxvZ2ZvbGRjaGFuZ2VzKSAlPiUgCiAgICBwaXZvdF93aWRlcih2YWx1ZXNfZnJvbSA9IGxvZ2ZvbGRjaGFuZ2VzLCBuYW1lc19mcm9tID0gYmFzZSkKICAKICBtYXQgPC0gdGliICU+JSBzZWxlY3QoLTEpICU+JSBhcy5tYXRyaXgoKQogIHJvdy5uYW1lcyhtYXQpIDwtIHRpYiAlPiUgcHVsbCgxKQogIAogIGhhX2NvbHVtbiA9IENvbXBsZXhIZWF0bWFwOjpIZWF0bWFwQW5ub3RhdGlvbihkZiA9IGRhdGEuZnJhbWUoVGFyZ2V0ID0gaWZlbHNlKGdyZXBsKHRhcmdldCwgY29sbmFtZXModGliKVstMV0pLCAiVGFyZ2V0IiwiTm90IiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSZW1vdmUgPSBpZmVsc2Uoc3RyX2V4dHJhY3QoY29sbmFtZXModGliKVstMV0sICdcXGQrJykgJWluJSByZW1vdmUsICJSZW1vdmUiLCJSZXRhaW4iKSksICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gbGlzdChUYXJnZXQgPSBjKCJUYXJnZXQiID0gImJsYWNrIiwiTm90IiA9ICJ3aGl0ZSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlbW92ZSA9IGMoIlJlbW92ZSIgPSAicmVkIiwgIlJldGFpbiIgPSAid2hpdGUiKSkpCiAgCiAgY29sX2Z1biA9IGNpcmNsaXplOjpjb2xvclJhbXAyKGMoLTMsIDAsIDMpLCBjKCJibHVlIiwgIndoaXRlIiwgInJlZCIpKQogIGRyYXcoSGVhdG1hcChtYXQsIGNvbD1jb2xfZnVuLAogICAgICAgICAgICAgICBuYW1lID0gJ2xvZ0ZvbGRDaGFuZ2UnLAogICAgICAgICAgICAgICB0b3BfYW5ub3RhdGlvbiA9IGhhX2NvbHVtbikKICApCn0KYGBgCiMjIFF1aWNrIENoZWNrIG9mIERpc2NyZXBhbmNpZXMgd2hlbiBmaWx0ZXJpbmcgb3V0IGxvd2VyIGNvbmZpZGVuY2Ugc2NvcmVzCmBgYHtyfQptY3QgPSBvYnMkb2JzICU+JSBncm91cF9ieShsZWlkZW41LCBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIpICU+JSBjb3VudCgpICU+JSB1bmdyb3VwKCkgJT4lIGdyb3VwX2J5KGxlaWRlbjUpICU+JSBzbGljZV9tYXgob3JkZXJfYnkgPSBuLCBuID0gMSkKZmlsdGVyX21jdCA9IG9icyRvYnMgJT4lIGZpbHRlcihDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXJfX21heF9zY29yZSA+IDAuOSkgJT4lIGdyb3VwX2J5KGxlaWRlbjUsIENUX19zY2VpYWRfMjAyNTAyMTFfZGV2X21tR2VuZUZpbHRlcikgJT4lIGNvdW50KCkgJT4lIHVuZ3JvdXAoKSAlPiUgZ3JvdXBfYnkobGVpZGVuNSkgJT4lIHNsaWNlX21heChvcmRlcl9ieSA9IG4sIG4gPSAxKQptY3QgJT4lIGxlZnRfam9pbihmaWx0ZXJfbWN0LCBieSA9ICdsZWlkZW41JykgJT4lIGZpbHRlcihDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIueCAhPSBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIueSkKYGBgCiMjIENUIENoYW5nZXMKYGBge3J9CmN0X21hcCA8LSBjKAogICcyMScgPSAnY29uZSAobWwpJywKICAnNjEnID0gJ3BlcmlvY3VsYXIgbWVzZW5jaHltZScsCiAgJzYyJyA9ICdjaWxpYXJ5IG1hcmdpbicsCiAgJzY1JyA9ICdyb2QgKHByZWN1cnNvciknLAogICc4NScgPSAnbGVucycsCiAgJzc3JyA9ICdhc3Ryb2N5dGUnLAogICcxMycgPSAncmV0aW5hbCBnYW5nbGlvbicsCiAgJzQ0JyA9ICdyZXRpbmFsIGdhbmdsaW9uJywKICAnNTEnID0gJ3JldGluYWwgZ2FuZ2xpb24nLAogICc0NycgPSAnaG9yaXpvbnRhbCcsCiAgJzE0JyA9ICduZXVyb2dlbmljJywgIyBjaGVjayBhYm92ZSB3aXRoIHNjb3JlIGRpc2NyZXBhbmN5IAogICc4NycgPSAnbmV1cm9nZW5pYycsICMgY2hlY2sgYWJvdmUgd2l0aCBzY29yZSBkaXNjcmVwYW5jeQogICc3MCcgPSAnYmlwb2xhciAocHJlY3Vyc29yKScgKSAjIGNoZWNrIGFib3ZlIHdpdGggc2NvcmUgZGlzY3JlcGFuY3kKCmxhYmVscyA8LSBvYnMkbGFiZWxzICU+JSAKICBtdXRhdGUoQ1QgPSBpZmVsc2UoYXMuY2hhcmFjdGVyKGxlaWRlbjUpICVpbiUgbmFtZXMoY3RfbWFwKSwgY3RfbWFwW2FzLmNoYXJhY3RlcihsZWlkZW41KV0sIG1DVF9zY3ZpKSkKCnJlbW92ZV9sZWlkZW41IDwtIGMoNDMsNzQpICMgb2RkIHVtYXAgYW5kIG1hcmtlciBleHByZXNzaW9uIChtaXhlZCBjdCkKYGBgCgojIyBIZWF0bWFwcwojIyMgUlBDIC8gTmV1cm9nZW5pYwpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTd9Cm1hcmtlcnMgPC0gYygiSEVTMSIsCiAgICAgICAgICAgICAiWkZQMzZMMiIsCiAgICAgICAgICAgICAjICJIRVM2IiwKICAgICAgICAgICAgICMgIkFUT0g3IiwKICAgICAgICAgICAgICJWSU0iLAogICAgICAgICAgICAgIkNDTkQxIiwKICAgICAgICAgICAgICJTRlJQMiIsCiAgICAgICAgICAgICAiU1BQMSIsCiAgICAgICAgICAgICAiWkZQMzZMMSIsCiAgICAgICAgICAgICAiVEYiLAogICAgICAgICAgICAgIkZPUyIsCiAgICAgICAgICAgICAiVFRZSDEiKSAlPiUgc3RyX3RvX3RpdGxlKCkKbWVsbG91Z2hfbWFya2VycyA8LSByZWFkX2Nzdigifi9naXQvZXllTWFya2Vycy9saXN0cy9ycGNfbWFya2Vyc19fTWVsbG91Z2gyMDE5LmNzdiIpCgpyZW1vdmVfbGVpZGVuNSA8LSBjKCkKCm1hcmtlcnMgPC0gbWVsbG91Z2hfbWFya2VycyAlPiUgZmlsdGVyKGBDZWxsIFR5cGVgID09ICdSUEMnKSAlPiUgcHVsbChIR05DKSAlPiUgc3RyX3RvX3RpdGxlKCkKbW9yZSA8LSBjKCJQQVg2IiwiTkVVUk9EMSIsIkFUT0g3IiwiSEVTNiIpICU+JSBzdHJfdG9fdGl0bGUoKQpobV9tYWtlcihjKG1hcmtlcnMsIG1vcmUpLCAicnBjfG5ldXJvIiwgY2xhYmVscyA9IG9icyRsYWJlbHMgJT4lIG11dGF0ZShDVCA9IG1DVCkpCgpgYGAKCgoKCiMjIyBQZXJpb2N1bGFyIE1lc2VuY2h5bWUgLyBFbmRvIC8gRXBpIC8gS2VyYXRvY3l0ZQpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTZ9Cm1hcmtlcnMgPC0gYygiTFVNIiwiRENOIiwiVklNIiwiUERHRlJBIiwiQ09MMUEyIiwgIyBodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MjAwMy0wMjAtMDkyMi00CiAgICAgICAgICAgICAiTUdQIiwiTUVHMyIsIkRDTiIsIkFQT0QiLCJBTkdQVEw3IiwiRUZFTVAxIiwiQk1QNSIsIlBSUlgxIikKCm1hcmtlcnMgPC0gI2MoIlZJTSIsIkZBUCIsIkNPTDFBMSIsIlBER0ZSQiIsIlMxMDBBNCIsICMgZmlicm8KICBjKCJQRU5LIiwgIlBJVFgyIiwgI1BPTQogICAgIkNESDUiLCJWV0YiLCAjIGVuZG8KICAgICJNWUY1IiwgIlVTUDE4IiwgI2Nvbm5lY3RpdmUgdGlzc3VlCiAgICAiQ0RIMSIsIktSVDE5IiwiRVBDQU0iLCAjIGVwaQogICAgIktFUkEiLCMga2VyYXRvY3l0ZSkKICAgICJBMk0iKSAKaG1fbWFrZXIobWFya2VycyAlPiUgc3RyX3RvX3RpdGxlKCksICJwZXJpb2N1fGVuZG98ZXBpfGtlcmEiLGNsYWJlbHMgPSBsYWJlbHMpCmBgYAoKIyMgTGVucwpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTEyfQptYXJrZXJzIDwtIGNvbnZfdGFibGUgJT4lIGZpbHRlcihncmVwbCgiXkNyeSIsIFNZTUJPTCkpICU+JSBwdWxsKFNZTUJPTCkKaG1fbWFrZXIobWFya2VycyAlPiUgc3RyX3RvX3RpdGxlKCksICJsZW5zIixjbGFiZWxzID0gbGFiZWxzKQpgYGAKCiMjIFJQRQpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTR9Cm1hcmtlcnMgPC0gYygiUnBlNjUiLCJNaXRmIiwiQmVzdDEiKQpobV9tYWtlcihtYXJrZXJzICU+JSBzdHJfdG9fdGl0bGUoKSwgInJwZSIsY2xhYmVscyA9IGxhYmVscykKYGBgCgojIyBBc3Ryb2N5dGUKYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD00fQptYXJrZXJzIDwtIGMoICJHZmFwIiwiUGF4MiIpCmhtX21ha2VyKG1hcmtlcnMgJT4lIHN0cl90b190aXRsZSgpLCAiYXN0cm9jIixjbGFiZWxzID0gbGFiZWxzKQpgYGAKCiMjIE11ZWxsZXIKYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD02fQptYXJrZXJzIDwtIGMoICJHbHVsIiwiQ2x1IiwiQXBvZSIpCmhtX21ha2VyKG1hcmtlcnMgJT4lIHN0cl90b190aXRsZSgpLCAiYXN0cm9jIixjbGFiZWxzID0gbGFiZWxzKQpgYGAKCiMjIEhvcml6b250YWwKYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD01fQoKbWFya2VycyA8LSBjKCJMSFgxIiwiT05FQ1VUMSIpICU+JSBzdHJfdG9fdGl0bGUoKQoKaG1fbWFrZXIobWFya2VycywgImhvcmkiKQoKYGBgCgojIyBQaG90b3JlY2VwdG9ycwpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTV9CgptYXJrZXJzIDwtICBjKCdBUlIzJywnT1BOMUxXJywnT1BOMVNXJywnUkhPJywgJ09QTjFNVycsICdSQ1ZSTicsIkNSWCIsIlBST00xIiwiQ05HQTEiLCJQREU2QSIpICU+JSBzdHJfdG9fdGl0bGUoKQojbWFya2VycyA8LSAgbWVsbG91Z2hfbWFya2VycyAlPiUgZmlsdGVyKGBDZWxsIFR5cGVgICVpbiUgYygnUm9kJywnQ29uZScpKSAlPiUgcHVsbChIR05DKQpobV9tYWtlcihtYXJrZXJzLCAicm9kfGNvbmUiKQoKYGBgCgoKIyMgQmlwb2xhcgpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTV9Cm1hcmtlcnMgPC0gYygiR1JJSzEiLCJJUlg2IiwiTFJUTTEiLCJQQ1AyIiwiUFJLQ0EiLCJUUlBNMSIsIlZTWDEiLCJWU1gyIikgJT4lIHN0cl90b190aXRsZSgpCiNtYXJrZXJzIDwtIG1lbGxvdWdoX21hcmtlcnMgJT4lIGZpbHRlcihgQ2VsbCBUeXBlYCA9PSAnQmlwb2xhcicpICU+JSBwdWxsKEhHTkMpCmhtX21ha2VyKG1hcmtlcnMsICJiaXBvbGFyIikKCmBgYAoKIyMgQW1hY3JpbmUKYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD01fQoKbWFya2VycyA8LSBjKCdHQUQxJywnR0FEMicsJ1NMQzZBOScsJ05GSUEnKSAlPiUgc3RyX3RvX3RpdGxlKCkKbWFya2VycyA8LSBtZWxsb3VnaF9tYXJrZXJzICU+JSBmaWx0ZXIoYENlbGwgVHlwZWAgPT0gJ0FtYWNyaW5lJykgJT4lIHB1bGwoSEdOQykgJT4lIHN0cl90b190aXRsZSgpCmhtX21ha2VyKG1hcmtlcnMsICJhbWFjciIpCgpgYGAKCiMjIEdhbmdsaW9uCgpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTV9CgptYXJrZXJzIDwtICBtZWxsb3VnaF9tYXJrZXJzICU+JSBmaWx0ZXIoYENlbGwgVHlwZWAgPT0gJ1JHQycpICU+JSBwdWxsKEhHTkMpICU+JSBzdHJfdG9fdGl0bGUoKQpobV9tYWtlcihtYXJrZXJzLCAiZ2FuZ2xpb24iKQoKYGBgCgoKCgojIFVwZGF0ZWQgVU1BUAoKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0Kbm9icyA8LSBvYnMkb2JzICU+JSAKICBsZWZ0X2pvaW4obGFiZWxzLCBieSA9ICdsZWlkZW41JykgJT4lIAogIGZpbHRlcighbGVpZGVuNSAlaW4lIHJlbW92ZV9sZWlkZW41KSAKCiMgb2JzJG9icyAlPiUgZmlsdGVyKGxlaWRlbjUgJWluJSBjKDE0LDU3KSkgJT4lIG1jSGVscGVSczo6c3VtX3JhdChsZWlkZW41LCBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIpIAojICMgdHdlYWtpbmcgZm9yIGNsdXN0ZXIgMTQgYW5kIDU3IGFzIHRoZXNlIGRvbid0IHNlZW0gdG8gYmUgY2xlYXJseSBiaXBvbGFyIHByZWN1cnNvcnMKIyBub2JzIDwtIG5vYnMgJT4lIG11dGF0ZShDVCA9IGNhc2Vfd2hlbihsZWlkZW41ID09IDE0ICYgQ1RfX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyID09ICdiaXBvbGFyIChwcmVjdXJzb3IpJyB+ICdyb2QgKHByZWN1cnNvciknLAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlaWRlbjUgPT0gMTQgJiBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIgPT0gJ25ldXJvZ2VuaWMnIH4gJ25ldXJvZ2VuaWMnLAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlaWRlbjUgPT0gMTQgJiBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIgPT0gJ3JvZCAocHJlY3Vyc29yKScgfiAncm9kIChwcmVjdXJzb3IpJywKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZWlkZW41ID09IDE0ICYgQ1RfX3NjZWlhZF8yMDI1MDIxMV9kZXZfbW1HZW5lRmlsdGVyID09ICdjb25lIChwcmVjdXJzb3IpJyB+ICdyb2QgKHByZWN1cnNvciknLAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlaWRlbjUgPT0gNTcgJiBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIgPT0gJ2JpcG9sYXIgKHByZWN1cnNvciknIH4gJ3JvZCAocHJlY3Vyc29yKScsCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVpZGVuNSA9PSA1NyAmIENUX19zY2VpYWRfMjAyNTAyMTFfZGV2X21tR2VuZUZpbHRlciA9PSAncm9kIChwcmVjdXJzb3IpJyB+ICdyb2QgKHByZWN1cnNvciknLAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlaWRlbjUgPT0gNTcgJiBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIgPT0gJ25ldXJvZ2VuaWMnIH4gJ25ldXJvZ2VuaWMnLAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlaWRlbjUgPT0gNTcgJiBDVF9fc2NlaWFkXzIwMjUwMjExX2Rldl9tbUdlbmVGaWx0ZXIgPT0gJ2JpcG9sYXInIH4gJ2JpcG9sYXInLAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlaWRlbjUgPT0gNTcgfiAnbmV1cm9nZW5pYycsCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IENUCiMgKSkKbm9icyAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gQ1QpLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoQ1QpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVtYXAyID0gbWVkaWFuKHVtYXAyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSBDVCwgY29sb3IgPSBDVCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZ2d0aXRsZSgiQ1QiKSAgCmBgYAoKIyBTdGFnZSAzIApPdXRwdXQgdXBkYXRlZCBDVCBjYWxscywgdXBkYXRlIHRoZSBoNWFkLCBhbmQgcmUtcnVuIHNjVkkKCmBgYHtyfQpzZXQuc2VlZCgyMDI1LTAzLTA1KQpyZWYgPC0gbm9icyAlPiUgCiAgZmlsdGVyKHNjQU5WSV9NQ1RfbWF4X3Njb3JlID4gMC45KSB8PiAKICBncm91cF9ieShzdHVkeV9hY2Nlc3Npb24sIENUKSAlPiUgCiAgc2xpY2Vfc2FtcGxlKG4gPSAxMDAwLCByZXBsYWNlID0gVFJVRSkgJT4lIAogIHVuaXF1ZSgpCgpxdWVyeSA8LSBub2JzICU+JSBmaWx0ZXIoIWJhcmNvZGVpICVpbiUgcmVmJGJhcmNvZGVpKQoKCiMgcmVmJGJhcmNvZGVpICU+JSB3cml0ZShnemZpbGUoJ34vZ2l0L3NjRWlhRF9tb2RlbGluZy9kYXRhL21tMTExX2Rldl9leWVfcmVmX2Jjcy4yMDI1MDMwNS5zdGFnZTMuY3N2Lmd6JykpCiMgcXVlcnkkYmFyY29kZWkgJT4lIHdyaXRlKGd6ZmlsZSgnfi9naXQvc2NFaWFEX21vZGVsaW5nL2RhdGEvbW0xMTFfZGV2X2V5ZV9xdWVyeV9iY3MuMjAyNTAzMDUuc3RhZ2UzLmNzdi5neicpKQojICMKIyBub2JzICU+JSBkcGx5cjo6cmVuYW1lKGJhcmNvZGUgPSBiYXJjb2RlaSkgJT4lIHdyaXRlX2Nzdignfi9naXQvc2NFaWFEX21vZGVsaW5nL2RhdGEvTW91c2VfRGV2ZWxvcGluZ19FeWVfX3N0YWdlM19DVGNhbGxzLmZyZWV6ZTIwMjUwMzA1LjAxLmNzdi5neicpCmBgYAoKYGBge2Jhc2ggYmlvd3VsZjIgYXBwZW5kIG9icywgZXZhbCA9IEZBTFNFfQpjZCAvZGF0YS9PR1ZGQl9CRy9zY0VpYUQvMjAyNF8wMl8yOC9zbmFrZW91dC9oczExMV9kZXZlbG9waW5nX2V5ZS9zdGFnZTQKc291cmNlIC9kYXRhLyRVU0VSL2NvbmRhL2V0Yy9wcm9maWxlLmQvY29uZGEuc2ggJiYgc291cmNlIC9kYXRhLyRVU0VSL2NvbmRhL2V0Yy9wcm9maWxlLmQvbWFtYmEuc2gKbWFtYmEgYWN0aXZhdGUgcmFwaWRzX3NpbmdsZWNlbGwKCnB5dGhvbiB+L2dpdC9zY0VpYURfbW9kZWxpbmcvd29ya2Zsb3cvc2NyaXB0cy9hcHBlbmRfb2JzLnB5IC4uLy4uL21tMTExLmFkYXRhLnNvbG8uMjAyNTAxMzEuaDVhZCAvaG9tZS9tY2dhdWdoZXlkL2dpdC9zY0VpYURfbW9kZWxpbmcvZGF0YS9Nb3VzZV9EZXZlbG9waW5nX0V5ZV9fc3RhZ2UzX0NUY2FsbHMuZnJlZXplMjAyNTAzMDUuMDEuY3N2Lmd6ICBtbTExMS5hZGF0YS5zb2xvLjIwMjUwMzA1LmRldi5zdGFnZTMuaDVhZCAtLXRyYW5zZmVyX2NvbHVtbnMgQ1QKYGBgCgojIyBvYnMgczMKYGBge3J9Cm9ic19zMyA8LSBwdWxsX29icygnfi9kYXRhL3NjRWlhRF9tb2RlbGluZy9tbTExMV9kZXZlbG9waW5nX2V5ZS9zdGFnZTMvbW0xMTFfZGV2X2V5ZV8yMDI1MDMwNV9zdGFnZTNfMjAwMGh2Z18yMDBlXzMwbC5vYnMuY3N2Lmd6JywgbGFiZWwgPSAnQ1QnLCBtYWNoaW5lX2xhYmVsID0gJ3NjQU5WSV9DVCcsIGNsdXN0ZXJfY29sID0gJ2xlaWRlbjUnKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD05fQoKb2JzX3MzJG9icyAlPiUgIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IENUKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IC4gJT4lIGdyb3VwX2J5KENUKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gQ1QsIGNvbG9yID0gQ1QpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArIAogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGdndGl0bGUoIkNUIikgIAoKCgpvYnNfczMkb2JzICU+JQogIGxlZnRfam9pbihvYnNfczMkbGFiZWxzLCBieSA9ICdsZWlkZW41JykgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IGFzLmZhY3RvcihsZWlkZW41KSksIHBvaW50c2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjUpICsKICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoZGF0YSA9IC4gJT4lIGdyb3VwX2J5KG1DVCwgbGVpZGVuNSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpLCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IHBhc3RlMChtQ1QsJy0nLGxlaWRlbjUpLGNvbG9yID0gYXMuZmFjdG9yKGxlaWRlbjUpKSwgYmcuY29sb3IgPSAnd2hpdGUnKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSwgcGFsczo6YWxwaGFiZXQoKSwgcGFsczo6a2VsbHkoKSwgcGFsczo6YnJld2VyLnNldDEoMTApLCBwYWxzOjpva2FiZSgpLCBwYWxzOjp3YXRsaW5ndG9uKCkpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBnZ3RpdGxlKCJsZWlkZW41IikgCgpgYGAKCiMjIENvbmZ1c2lvbiBNYXRyaXgKCgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQptYWNoaW5lX2xhYmVsID0gJ3NjQU5WSV9DVCc7IGxhYmVsID0gJ0NUJwpvYnNfczMkb2JzICU+JSAKICAjZmlsdGVyKHNjQU5WSV9DVF9tYXhfc2NvcmUgPiAwLjkpICU+JSAKICBncm91cF9ieSguZGF0YVtbbGFiZWxdXSwuZGF0YVtbbWFjaGluZV9sYWJlbF1dKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUgCiAgbXV0YXRlKFJhdGlvID0gQ291bnQvc3VtKENvdW50KSkgJT4lIAogIGdncGxvdChhZXMoeD0uZGF0YVtbbGFiZWxdXSx5PS5kYXRhW1ttYWNoaW5lX2xhYmVsXV0sZmlsbD1SYXRpbywgbGFiZWwgPSByb3VuZChSYXRpbywgMikpKSArIAogIGdlb21fdGlsZSgpICsgCiAgc2hhZG93dGV4dDo6Z2VvbV9zaGFkb3d0ZXh0KCkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKGJlZ2luID0gMCwgZW5kID0gMSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkgCmBgYAoKYGBge3J9CiMgcmV0YWlucyBDVCBjYWxscyA+PSAwLjA1IG9mIGEgY2x1c3Rlci4gYW55dGhpbmcgYmVsb3cgZ2V0cyBjaGFuZ2VkIHRvIHRoZSBkb21pbmFudCBjdApub2JzX3MzX2NsZWFuaW5nIDwtIG9ic19zMyRvYnMgJT4lIAogIGdyb3VwX2J5KGxlaWRlbjUsIHNjQU5WSV9DVCkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lCiAgbXV0YXRlKFJhdGlvID0gQ291bnQgLyBzdW0oQ291bnQpKSAlPiUgCiAgbXV0YXRlKGRvbWluYW50X2NlbGx0eXBlID0gc2NBTlZJX0NUW3doaWNoLm1heChDb3VudCldKSAlPiUgCiAgbXV0YXRlKENUYyA9IGNhc2Vfd2hlbihSYXRpbyA8IDAuMDUgfiBkb21pbmFudF9jZWxsdHlwZSwKICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiBzY0FOVklfQ1QpKQoKbm9ic19zMyA8LSBvYnNfczMkb2JzICU+JSAKICBsZWZ0X2pvaW4obm9ic19zM19jbGVhbmluZyAlPiUgCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsZWlkZW41LCBzY0FOVklfQ1QsIENUYyksIGJ5ID0gYygibGVpZGVuNSIsInNjQU5WSV9DVCIpKQpub2JzX3MzIDwtIG5vYnNfczMgJT4lIHNlbGVjdCgtdW1hcDEsIC11bWFwMikgJT4lIGxlZnRfam9pbihvYnMkb2JzICU+JSBkcGx5cjo6c2VsZWN0KHVtYXAxLHVtYXAyLCBiYXJjb2RlaSksIGJ5ID0gJ2JhcmNvZGVpJykKCm5vYnNfczMgJT4lIG1jSGVscGVSczo6c3VtX3JhdChDVCwgQ1RjLHRocmVzaG9sZCA9IDAuMDEpCmBgYAoKIyBGaW5hbCBVTUFQCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD05fQoKbm9ic19zMyAlPiUgIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IENUYyksIHBvaW50c2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjUpICsKICBnZ3JlcGVsOjpnZW9tX2xhYmVsX3JlcGVsKGRhdGEgPSAuICU+JSBncm91cF9ieShDVGMpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVtYXAyID0gbWVkaWFuKHVtYXAyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSBDVGMsIGNvbG9yID0gQ1RjKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCkpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBnZ3RpdGxlKCJDVGMiKSAgCmBgYAojIyAiVHJhbnNpdGlvbmFsIiAKUGxvdCBvZiB0aGUgTUwgY2VsbCB0eXBlIGNhbGwgY29uZmlkZW5jZSAoMSBpcyAxMDAlIGNvbmZpZGVuY2UpLiBXZSBzZWUgdGhhdCBpbiBzZXZlcmFsICJwcmVjdXJzb3IiIHBvc2l0aW9ucyBpbiB0aGUgVU1BUCB0aGUgY29uZmlkZW5jZSBpcyBtdWNoIGxvd2VyLiAKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTl9Cm5vYnNfczMgJT4lICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBzY0FOVklfQ1RfbWF4X3Njb3JlKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IC4gJT4lIGdyb3VwX2J5KENUYykgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodW1hcDEgPSBtZWRpYW4odW1hcDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IENUYyksIGNvbG9yID0gJ2JsYWNrJykgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhvcHRpb24gPSAncm9ja2V0JykgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyAKICBnZ3RpdGxlKCJDVGMiKSAgCmBgYAoKCiMgT3V0cHV0CmBgYHtyfQojbm9ic19zMyAlPiUgIAojICB3cml0ZV9jc3YoIn4vZGF0YS9zY0VpYURfbW9kZWxpbmcvbW0xMTFfZGV2ZWxvcGluZ19leWUvc3RhZ2UzL21tMTExX2Rldl9leWVfMjAyNTAzMDVfc3RhZ2UzXzIwMDBodmdfMjAwZV8zMGwubm9ic19zMy5jc3YuZ3oiKQpgYGAKCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYA==